package cn.com.blueInfo.core.util;

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;

/**
 * 雪花算法生成器（适配JDK8，可作为Spring Bean注入）
 * 结构：1位符号位 + 41位时间戳 + 10位机器ID + 12位序列号
 */
@Data
@Component
@ConfigurationProperties(prefix = "core.snowflake")
public class SnowflakeIdGenerator {

    //-------- 从配置文件中注入的参数
    // 起始时间戳（可自行修改，建议设置为项目上线时间，减少高位占用）
    private long startTimestamp;
    // 机器ID
    private long workerId;
    // 机器ID位数（最多支持1024台机器）
    private long workerIdBits;
    // 序列号位数（每毫秒最多生成4096个ID）
    private long sequenceBits;

    //-------- 计算得到的衍生参数
    // 机器ID最大值（10位：0-1023）
    private long maxWorkerId;
    // 序列号最大值（12位：0-4095）
    private long maxSequence;

    // 机器ID左移位数
    private long workerIdShift;
    // 时间戳左移位数
    private long timestampShift;

    //-------- 运行时的变量
    private long lastTimestamp = -1L; // 上一次生成ID的时间戳
    private long sequence = 0L; // 当前毫秒内序列号

    // 初始化参数（PostConstruct确保在注入后执行）
    @PostConstruct
    public void init() {
        // 计算最大值（位运算：~(-1L << n) 等价于 2^n - 1）
        maxWorkerId = ~(-1L << workerIdBits);
        maxSequence = ~(-1L << sequenceBits);

        // 计算移位位数
        workerIdShift = sequenceBits;
        timestampShift = sequenceBits + workerIdBits;

        // 校验workerId合法性
        if (workerId < 0 || workerId > maxWorkerId) {
            throw new IllegalArgumentException("Worker ID超出范围（0-" + maxWorkerId + "）");
        }
    }

    /**
     * 生成下一个唯一ID
     * @return 雪花算法ID（Long类型）
     */
    public synchronized long nextId() {
        long currentTimestamp = System.currentTimeMillis();

        // 如果当前时间戳小于上一次，说明时钟回拨，抛出异常（或根据业务处理）
        if (currentTimestamp < lastTimestamp) {
            throw new RuntimeException("时钟回拨，拒绝生成ID，差值：" + (lastTimestamp - currentTimestamp) + "ms");
        }

        // 同一毫秒内，序列号自增
        if (currentTimestamp == lastTimestamp) {
            sequence = (sequence + 1) & maxSequence;
            // 序列号满了，等待下一毫秒
            if (sequence == 0) {
                currentTimestamp = waitNextMillis(lastTimestamp);
            }
        } else {
            // 不同毫秒，序列号重置为0
            sequence = 0L;
        }

        lastTimestamp = currentTimestamp;

        // 组合ID：时间戳位 + 机器ID位 + 序列号位
        return ((currentTimestamp - startTimestamp) << timestampShift)
                | (workerId << workerIdShift)
                | sequence;
    }

    /**
     * 等待到下一毫秒
     */
    private long waitNextMillis(long lastTimestamp) {
        long timestamp = System.currentTimeMillis();
        while (timestamp <= lastTimestamp) {
            timestamp = System.currentTimeMillis();
        }
        return timestamp;
    }

}
